package v2;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PullResult;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.StopWalkException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/***
 *
 * @author luomengjun
 */
public class Deploy {
    private String appTargetPath;
    private String targetDir;
    private String zipTargetDir;
    private String javaPath = "src/main/java/";
    private String resourcePath = "src/main/resource/";
    private String resourcesPath = "src/main/resources/";
    private String webPath = "src/main/webapp/";
    private String classPath = null;
    private String workDir;
    private String name;
    private String password;
    private String url;
    private String mavenWorkDir;
    private String[] keyWords;
    private String localGitDirRoot;
    private List<String[]> dependencyList;
    private boolean showSource = false;
    private String sourceDir;
    private String appDir;
    private boolean rangeControl = true;
    private String branchName;
    private Git git;
    private CredentialsProvider cp;


    private Config cfg;

    public Deploy(Config config) {
        cfg = config;
    }

    private static class MyDiffEntry {
        public DiffEntry diffEntry;
        public int commitTime;
    }

    private void init() {
        workDir = cfg.workDir;
        name = cfg.name;
        password = cfg.password;
        url = cfg.url;
        dependencyList = cfg.dependencyList;
        localGitDirRoot = cfg.localGitDirRoot;
        branchName = cfg.branchName;
        appDir = cfg.appDir;
        mavenWorkDir = localGitDirRoot + appDir + "/";
        keyWords = cfg.keyWords;
        showSource = cfg.showSource;
        rangeControl = cfg.rangeControl;
        appTargetPath = mavenWorkDir + "target/";
        classPath = mavenWorkDir + "target/classes/";
        targetDir = workDir + appDir + "/";
        sourceDir = workDir + appDir + "_source/";
        zipTargetDir = workDir + appDir + ".zip";

        if (!initWorkDir(targetDir)) {
            System.out.println("初始化工作目录失败");
            return;
        }

        System.out.println("初始化工作目录完成 " + targetDir + "\r\n");

        if (showSource) {
            if (!initWorkDir(sourceDir)) {
                System.out.println("初始化源代码目录失败");
                return;
            }
            System.out.println("初始化源文件目录完成 " + sourceDir + "\r\n");
        }
    }

    @SuppressWarnings({"rawtypes", "deprecation"})
    public void run() throws Exception {
        init();

        System.out.println("从git上更新代码开始 ******************************************************************\r\n");
        for (String[] arr : dependencyList) {
            updateGitProject(localGitDirRoot + arr[0] + "/", arr[1]);
        }
        updateGitProject(mavenWorkDir, url);
        System.out.println("");
        System.out.println("从git上更新代码完成 ******************************************************************\r\n");

        //删除target文件夹
        if (new File(appTargetPath).exists()) {
            deleteDir(new File(appTargetPath));
        }

        System.out.println("maven编译代码开始 ******************************************************************");
        mvnCompile(mavenWorkDir);
        System.out.println("maven编译代码完成 ******************************************************************\r\n");

        List<MyDiffEntry> changesList = null;
        if (keyWords != null) {
            System.out.println("读取项目修改记录开始 ******************************************************************\r\n");
            changesList = showLogs();
            System.out.println("\r\n读取项目修改记录结束 ******************************************************************\r\n");
        }

        if (changesList == null || changesList.isEmpty()) {
            //删除target文件夹
            if (new File(appTargetPath).exists()) {
                deleteDir(new File(appTargetPath));
            }
            System.out.println("没有匹配到修改记录\r\n");
            return;
        }

        Map<String, MyDiffEntry> deletedMap = new HashMap<String, MyDiffEntry>();
        for (MyDiffEntry entry : changesList) {
            if (entry.diffEntry.getChangeType().equals(DiffEntry.ChangeType.DELETE)) {
                String path = getFilePath(entry.diffEntry);
                if (deletedMap.containsKey(path)) {
                    if (deletedMap.get(path).commitTime < entry.commitTime) {
                        deletedMap.put(path, entry);
                    }
                } else {
                    deletedMap.put(path, entry);
                }
            }
        }

        System.out.println("过滤后的文件 ******************************************************************");
        ArrayList<DiffEntry> fileList = new ArrayList<DiffEntry>();
        //过滤一些不要的文件
        for (MyDiffEntry myEntry : changesList) {
            DiffEntry entry = myEntry.diffEntry;
            String filePath = getFilePath(entry);
            if (deletedMap.containsKey(filePath) && deletedMap.get(filePath).commitTime >= myEntry.commitTime) {
                continue;
            }
            if (rangeControl && filePath.indexOf(appDir) < 0) {
                continue;
            }
            fileList.add(entry);
            System.out.println(filePath);
        }
        System.out.println("");

        for (DiffEntry entry : fileList) {
            String fileName = getFilePath(entry);
            String fullFilePath = localGitDirRoot + fileName;
            if (new File(fullFilePath).isDirectory()) {
                // 空文件目录也需要创建
                createDirOnly(fileName);
                continue;
            }
            if (fileName.indexOf(javaPath) > -1) {
                fileName = getFileName(javaPath, fileName);
                if (fileName.endsWith(".java")) {
                    String fromfileName = classPath + fileName.replaceAll("\\.java", ".class");
                    String toFileName = targetDir + "WEB-INF/classes/" + fileName.replaceAll("\\.java", ".class");
                    fileMove(fromfileName, toFileName, true);
                    // 这里处理内部类
                    copySubClass(fromfileName, toFileName);
                } else {
                    fileMove(fullFilePath, targetDir + "WEB-INF/classes/" + fileName, true);
                }
            } else if (fileName.indexOf(resourcePath) > -1 || fileName.indexOf(resourcesPath) > -1) {
                fileName = getFileName(resourcesPath, fileName);
                fileName = getFileName(resourcePath, fileName);
                fileMove(fullFilePath, targetDir + "WEB-INF/classes/" + fileName, true);
            } else if (fileName.indexOf(webPath) > -1) {
                fileName = getFileName(webPath, fileName);
                fileMove(fullFilePath, targetDir + fileName, true);
            }

            if (showSource) {
                fileName = getFileName(appDir, entry.getNewPath());
                fileMove(fullFilePath, sourceDir + fileName, false);
            }
        }
        //删除target目录
        if (new File(appTargetPath).exists()) {
            deleteDir(new File(appTargetPath));
        }
        System.out.println("\r\n\r\n开始进行zip压缩");
        zipMultiFile(targetDir, zipTargetDir);
        System.out.println("开始进行zip压缩完成 " + zipTargetDir);
        // 调用windows资源管理器打开你的部署目录
        callCmd2("explorer " + workDir.replaceAll("/", "\\\\"));
    }

    private List<MyDiffEntry> showLogs() throws Exception {

        List<MyDiffEntry> retList = new ArrayList<MyDiffEntry>();

        List<RevCommit> allCommitList = new ArrayList<RevCommit>();

        Iterator<RevCommit> iterator = git.log().all().call().iterator();
        do {
            RevCommit commit = iterator.next();
            allCommitList.add(commit);
        } while (iterator.hasNext());


        Collections.sort(allCommitList, new Comparator<RevCommit>() {
            @Override
            public int compare(RevCommit o1, RevCommit o2) {
                return new Integer(o1.getCommitTime()).compareTo(new Integer(o2.getCommitTime()));
            }
        });

        Map<ObjectId, RevCommit> lastCommitMap = new HashMap<ObjectId, RevCommit>();
        RevCommit lastCommit = null;
        for (RevCommit commit : allCommitList) {
            lastCommitMap.put(commit.getId(), lastCommit);
            lastCommit = commit;
        }


        System.out.println(appDir + " 应用下所有的提交记录");
        //只获取相关应用目录
        iterator = git.log().call().iterator();
        do {
            RevCommit commit = iterator.next();
            PersonIdent authoIdent = commit.getAuthorIdent();
            System.out.print(commit.getId());
            System.out.print("\t" + authoIdent.getName());
            System.out.print("\t" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(authoIdent.getWhen()));
            System.out.println("\t" + commit.getFullMessage().trim());
        } while (iterator.hasNext());

        RevFilter filter = new RevFilter() {
            @Override
            public boolean include(RevWalk revWalk, RevCommit revCommit) throws StopWalkException, MissingObjectException, IncorrectObjectTypeException, IOException {
                for (String msg : keyWords) {
                    String commitMsg = revCommit.getFullMessage().trim();
                    if (msg.equals(commitMsg)) {
                        return true;
                    }
                }
                return false;
            }

            @Override
            public RevFilter clone() {
                return this;
            }
        };

        Iterable<RevCommit> results = git.log().setRevFilter(filter).call();
        for (RevCommit commit : results) {
            System.out.println("\r\n匹配到的提交：" + commit.getFullMessage().trim());
            AbstractTreeIterator newTree = getCanonicalTreeParser(git, commit.getId());
            AbstractTreeIterator oldTree = getCanonicalTreeParser(git, lastCommitMap.get(commit.getId()).getId());
            List<DiffEntry> list = git.diff().setOldTree(oldTree).setNewTree(newTree).setOutputStream(null).call();
            for (DiffEntry entry : list) {
                MyDiffEntry myEntry = new MyDiffEntry();
                myEntry.diffEntry = entry;
                myEntry.commitTime = commit.getCommitTime();
                System.out.println(entry);
                retList.add(myEntry);
            }
        }
        return retList;
    }

    private void updateGitProject(String appDir, String gitUrl) throws Exception {
        cp = new UsernamePasswordCredentialsProvider(name, password);
        File gitRoot = new File(appDir);
        if (gitRoot.exists()) {
            git = Git.open(gitRoot);
        } else {
            git = Git.cloneRepository()
                    .setURI(gitUrl)
                    .setDirectory(gitRoot).setCredentialsProvider(cp)
                    .call();
        }

        //切换分支
        git.checkout().setCreateBranch(false).setName(branchName).call();
        //更新代码

        PullResult pullResult = git.pull().setCredentialsProvider(cp).call();
        System.out.println("\r\nfetch from " + pullResult.getFetchedFrom());
        System.out.println("fetch from url " + pullResult.getFetchResult().getURI().toString());
        System.out.println("mergeStatus：" + pullResult.getMergeResult().getMergeStatus());
        if (!pullResult.isSuccessful()) {
            System.err.println("git更新失败！");
            throw new RuntimeException("git更新失败！");
        }
        Status status = git.status().call();        //返回的值都是相对工作区的路径，而不是绝对路径
        System.out.println("\r\n更新后工作区状态信息-----------------");
        System.out.println("Added: " + status.getAdded());
        System.out.println("Changed: " + status.getChanged());
        System.out.println("Conflicting: " + status.getConflicting());
        System.out.println("ConflictingStageState: " + status.getConflictingStageState());
        System.out.println("IgnoredNotInIndex: " + status.getIgnoredNotInIndex());
        System.out.println("Missing: " + status.getMissing());
        System.out.println("Modified: " + status.getModified());
        System.out.println("Removed: " + status.getRemoved());
        System.out.println("Untracked: " + status.getUntracked());
        System.out.println("UntrackedFolders: " + status.getUntrackedFolders());

    }

    private String getFileName(String replaceStr, String path) {
        int index = path.indexOf(replaceStr);
        if (index > -1) {
            return path.substring(index + replaceStr.length());
        }
        return path;
    }

    private void copySubClass(String fromfileName, String toFileName) throws Exception {
        String fileName = fromfileName.substring(fromfileName.lastIndexOf("/") + 1);
        fileName = fileName.replaceAll("\\.class", "") + "$";
        String toPath = toFileName.substring(0, toFileName.lastIndexOf("/"));
        String currentDir = fromfileName.substring(0, fromfileName.lastIndexOf("/"));
        String toFile = "";
        String fromFile = "";
        File[] files = new File(currentDir).listFiles();
        String name;
        for (File file : files) {
            name = file.getName();
            if (name.indexOf(fileName) > -1) {
                fromFile = currentDir + "/" + file.getName();
                toFile = toPath + "/" + file.getName();
                fileMove(fromFile, toFile, true);
            }
        }
    }

    private void createDirOnly(String path) {
        if (path.indexOf(javaPath) > -1) {
            path = getFileName(javaPath, path);
            path = targetDir + "WEB-INF/classes/" + path;
        } else if (path.indexOf(resourcePath) > -1 || path.indexOf(resourcesPath) > -1) {
            path = getFileName(resourcesPath, path);
            path = getFileName(resourcePath, path);
            path = targetDir + "WEB-INF/classes/" + path;
        } else if (path.indexOf(webPath) > -1) {
            path = getFileName(webPath, path);
            path = targetDir + path;
        }
        if (!new File(path).exists()) {
            new File(path).mkdirs();
        }
    }

    private boolean initWorkDir(String targetDir) {
        try {
            deleteDir(new File(targetDir));
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return new File(targetDir).mkdirs();
    }

    private void fileMove(String fromfileName, String toFileName, boolean printTarget) throws Exception {
        if (printTarget) {
            System.out.println("copy " + fromfileName + "\t" + toFileName);
        }
        String parentDir = toFileName.substring(0, toFileName.lastIndexOf("/"));
        if (!new File(parentDir).exists()) {
            new File(parentDir).mkdirs();
        }
        FileInputStream srcFs = new FileInputStream(fromfileName);
        FileOutputStream destFs = new FileOutputStream(toFileName);
        byte[] bts = new byte[srcFs.available()];
        srcFs.read(bts);
        destFs.write(bts);
        srcFs.close();
        destFs.flush();
        destFs.close();
    }


    private boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        return dir.delete();
    }

    public void zipMultiFile(String filepath, String zippath) throws Exception {
        ZipOutputStream zipOut = null;
        try {
            File file = new File(filepath);
            File zipFile = new File(zippath);
            zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                for (File fileSec : files) {
                    recursionZip(zipOut, fileSec, "");
                }
            }
        } finally {
            if (zipOut != null) {
                zipOut.flush();
                zipOut.close();
            }
        }
    }

    private void recursionZip(ZipOutputStream zipOut, File file, String baseDir) throws Exception {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files.length > 0) {
                for (File fileSec : files) {
                    recursionZip(zipOut, fileSec, baseDir + file.getName() + "/");
                }
            } else {
                zipOut.putNextEntry(new ZipEntry(baseDir + file.getName() + "/"));
            }
        } else {
            byte[] buf = new byte[1024];
            InputStream input = new FileInputStream(file);
            zipOut.putNextEntry(new ZipEntry(baseDir + file.getName()));
            int len;
            while ((len = input.read(buf)) != -1) {
                zipOut.write(buf, 0, len);
            }
            input.close();
        }
    }

    public void callCmd(String locationCmd) {
        try {
            Process p = Runtime.getRuntime().exec(locationCmd);
            InputStream in = p.getInputStream();
            p.waitFor();
            byte[] b = new byte[in.available()];
            in.read(b, 0, b.length);
            System.out.println(new String(b));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void callCmd2(String locationCmd) {
        try {
            Runtime.getRuntime().exec(locationCmd);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void mvnCompile(String appWorkDir) throws Exception {
        String diskTag = appWorkDir.substring(0, appWorkDir.indexOf(":") + 1);
        String cmd = diskTag + " \r\n";
        cmd = cmd + "CD " + appWorkDir + " \r\n";
        cmd = cmd + "mvn compile";
        String path = workDir + "run.bat";
        FileOutputStream fs = new FileOutputStream(path);
        fs.write(cmd.getBytes("gb2312"));
        fs.flush();
        fs.close();
        // cmd /k start
        callCmd(" " + path);
    }

    private AbstractTreeIterator getCanonicalTreeParser(Git git, ObjectId commitId) throws IOException {
        try (RevWalk walk = new RevWalk(git.getRepository())) {
            RevCommit commit = walk.parseCommit(commitId);
            ObjectId treeId = commit.getTree().getId();
            try (ObjectReader reader = git.getRepository().newObjectReader()) {
                return new CanonicalTreeParser(null, reader, treeId);
            }
        }
    }

    private String getFilePath(DiffEntry entry) throws IOException {
        if (entry.getChangeType().equals(DiffEntry.ChangeType.DELETE)) {
            return entry.getOldPath();
        } else if (entry.getChangeType().equals(DiffEntry.ChangeType.ADD)) {
            return entry.getNewPath();
        } else if (entry.getChangeType().equals(DiffEntry.ChangeType.MODIFY)) {
            return entry.getOldPath();
        } else {
            return entry.getNewPath();
        }
    }

}
